---
title: Рисуем сердечко в консоли
description: Напишем два варианта алгоритма на Java для вывода сердечка в консоль в форме текстового изображения — поздравим женщин с восьмым марта. Нарисуем график...
sections: [Геометрические фигуры,Текстовое изображение,Отрисовка шрифта]
tags: [java,awt,консоль,ромб,окружность,круг,текст,изображение,шрифт]
canonical_url: /ru/2023/03/08/drawing-heart-in-console.html
url_translated: /en/2023/03/08/drawing-heart-in-console.html
title_translated: Drawing heart in console
date: 2023.03.08
---

Напишем два варианта алгоритма на Java для вывода сердечка в консоль в форме текстового
изображения — поздравим женщин с восьмым марта. Нарисуем график функции в форме сердечка
и в дополнение нарисуем символ *сердечко* в форме картинки, а картинку выведем текстом
— консольное поздравление с восьмым марта.

{% include heading.html text="График в форме сердечка" hash="heart-shaped-graph" %}

Нарисуем два полукруга и один полуромб, заполненные внутри и снаружи. В предыдущем примере мы выводили
[график функции в консоль]({{ '/ru/2023/02/05/function-graph-in-console.html' | relative_url }})
— формулы для окружности и для ромба возьмём из него, а в этом примере добавим заполнение фигуры внутри и
снаружи — вместо знака *равно* в формулах подставляем знаки *меньше* или *больше*. Условий получится много,
в отличие от предыдущего примера.

Нарисуем картинку для наглядности.

{% include picture.html src="/img/heart-graph.png" background=true
alt="График в форме сердечка — это два полукруга и один полуромб"
caption="Два полукруга и один полуромб" %}

Выводим верхнюю часть фигуры, нижнюю часть фигуры, закрашиваем в шахматном порядке и выводим
координатные оси. Получаем несколько текстовых изображений, которые выглядят следующим образом.

```
Радиус: 5, внутри/снаружи/оси: true/true/true.
  ·   ·   ·   ·   ·   ↑y  ·   ·   ·   ·   ·   
·   ·   o o o o o   · ¦ ·   o o o o o   ·   · 
  ·   o   *   *   o   ·   o   *   *   o   ·   
·   o   *   *   *   o ¦ o   *   *   *   o   · 
  o   *   *   *   *   o   *   *   *   *   o   
· o *   *   *   *   * o *   *   *   *   * o · 
--o --* --* --* --* --+---* --* --* --* --o >x
·   o   *   *   *   * ¦ *   *   *   *   o   · 
  ·   o   *   *   *   *   *   *   *   o   ·   
·   ·   o   *   *   * ¦ *   *   *   o   ·   · 
  ·   ·   o   *   *   *   *   *   o   ·   ·   
·   ·   ·   o   *   * ¦ *   *   o   ·   ·   · 
  ·   ·   ·   o   *   *   *   o   ·   ·   ·   
·   ·   ·   ·   o   * ¦ *   o   ·   ·   ·   · 
  ·   ·   ·   ·   o   *   o   ·   ·   ·   ·   
·   ·   ·   ·   ·   o ¦ o   ·   ·   ·   ·   · 
  ·   ·   ·   ·   ·   o   ·   ·   ·   ·   ·   
·   ·   ·   ·   ·   · ¦ ·   ·   ·   ·   ·   · 
```
{% capture collapsed_md %}
```
Радиус: 4, внутри/снаружи/оси: false/true/false.
·   ·   ·   ·   ·   ·   ·   ·   ·   · 
  ·   o o o o o   ·   o o o o o   ·   
·   o o       o o   o o       o o   · 
  o o           o o o           o o   
· o               o               o · 
  o                               o   
·   o                           o   · 
  ·   o                       o   ·   
·   ·   o                   o   ·   · 
  ·   ·   o               o   ·   ·   
·   ·   ·   o           o   ·   ·   · 
  ·   ·   ·   o       o   ·   ·   ·   
·   ·   ·   ·   o   o   ·   ·   ·   · 
  ·   ·   ·   ·   o   ·   ·   ·   ·   
·   ·   ·   ·   ·   ·   ·   ·   ·   · 
```
```
Радиус: 3, внутри/снаружи/оси: true/false/false.
    o o o       o o o     
  o *   * o   o *   * o   
o *   *   * o *   *   * o 
o   *   *   *   *   *   o 
  o   *   *   *   *   o   
    o   *   *   *   o     
      o   *   *   o       
        o   *   o         
          o   o           
            o             
```
```
Радиус: 2, внутри/снаружи/оси: false/false/false.
  o o o   o o o   
o       o       o 
o               o 
  o           o   
    o       o     
      o   o       
        o         
```
{% endcapture %}
{%- include collapsed_block.html summary="Полный вывод" content=collapsed_md -%}

Обходим диапазон координат двумя вложенными циклами `for`: сначала по оси `y` и затем по оси `x`.
Каждую точку проверяем на соответствие условиям и выводим. В верхней части рисуем два полукруга и
опционально закрашиваем их внутри и снаружи. В нижней части рисуем полуромб и также опционально
закрашиваем внутри и снаружи.

```java
/**
 * @param r    радиус
 * @param gap  отступ
 * @param in   заполнение внутри
 * @param out  заполнение снаружи
 * @param axes координатные оси
 */
public static void printHeartGraph(
    int r, int gap, boolean in, boolean out, boolean axes) {
  // границы текстового изображения
  int xMax = 2*r+gap, xMin = -xMax;
  int yMax = r+gap, yMin = -r-yMax;
  System.out.println( // заголовок с параметрами
      "Радиус: "+r+", внутри/снаружи/оси: "+in+"/"+out+"/"+axes+".");
  // вывод в консоль построчно слева направо сверху вниз
  for (int y = yMax; y >= yMin; y--) {
    for (int x = xMin; x <= xMax; x++) {
      double[] circle = { // две окружности левая/правая
          Math.round(Math.sqrt(Math.pow(x+r,2)+Math.pow(y,2))), // левая
          Math.round(Math.sqrt(Math.pow(x-r,2)+Math.pow(y,2)))}; // правая
      int rhombus = Math.abs(x)+Math.abs(y); // ромб
      boolean inCh = in && (x+y)%2 == 0; // шахматный порядок внутри
      boolean outCh = out && (x+y)%2 == 0; // шахматный порядок снаружи
      // каждую точку проверяем на соответствие условиям и выводим
      if (axes && y == 0 && x == 0)
        System.out.print("+-"); // начало координат
      else if (axes && y == 0 && x == xMax)
        System.out.print(">x"); // максимум оси абсцисс (x)
      else if (axes && x == 0 && y == yMax)
        System.out.print("↑y"); // максимум оси ординат (y)
      else if (y > 0 && (circle[0] == r || circle[1] == r))
        System.out.print("o "); // два полукруга, верх
      else if (y > 0 && inCh && (circle[0] < r || circle[1] < r))
        System.out.print("* "); // верх внутри
      else if (y > 0 && outCh && (circle[0] > r && circle[1] > r))
        System.out.print("· "); // верх снаружи
      else if (y <= 0 && rhombus == 2*r)
        System.out.print("o "); // полуромб, низ
      else if (y <= 0 && inCh && rhombus < 2*r)
        System.out.print("* "); // низ внутри
      else if (y <= 0 && outCh && rhombus > 2*r)
        System.out.print("· "); // низ снаружи
      else if (axes && y == 0)
        System.out.print("--"); // ось абсцисс (x)
      else if (axes && x == 0)
        System.out.print("¦ "); // ось ординат (y)
      else
        System.out.print("  "); // пустое место
    } // переход на новую строку
    System.out.println();
  }
}
```
```java
// запускаем программу и выводим результат
public static void main(String[] args) {
  printHeartGraph(5, 1, true, true, true);
  printHeartGraph(4, 1, false, true, false);
  printHeartGraph(3, 0, true, false, false);
  printHeartGraph(2, 0, false, false, false);
}
```

{% include heading.html text="Текст картинкой и картинка текстом" hash="text-as-picture-and-picture-as-text" %}

В предыдущем примере мы [рисовали простую капчу]({{ '/ru/2023/01/03/drawing-simple-captcha.html' | relative_url }})
— алгоритм отрисовки шрифта возьмём из него, только на этот раз нарисуем бинарное чёрно-белое
изображение моноширинным шрифтом, сглаживание не используем. Символ *сердечко* в форме картинки
выглядит следующим образом.

{% include picture.html src="/img/heart-monospaced-plain-22.bmp"
alt="Символ сердечко, шрифт моноширинный, обычный, 22"
title="Символ сердечко, шрифт моноширинный, обычный, 22" %}

Поскольку символ находится в середине строки, то на полученном изображении половина пикселей пустые.
Обходим пиксели построчно и выводим только непустые строки, то есть центральную часть изображения.

```
Monospaced.plain, 22, символы: ♡
      o           o o     
  o o   o o   o o     o   
o         o   o         o 
o           o           o 
o           o           o 
o                       o 
o                     o   
  o                   o   
    o               o     
      o           o       
        o       o         
        o     o           
          o   o           
            o             
```
{% capture collapsed_md %}
```
Monospaced.plain, 28, символы: ♡
        o               o         
    o o   o o       o o   o o     
  o           o   o           o   
o             o o             o   
o               o               o 
o               o               o 
o               o             o   
o                             o   
  o                           o   
    o                       o     
      o                   o       
        o               o         
        o             o           
          o         o             
            o       o             
              o   o               
                o                 
```
```
Monospaced.plain, 36, символы: ♡
        o o o               o o o o         
    o o       o o         o         o o     
  o               o     o             o     
  o               o   o                 o   
o                   o o                 o   
o                   o                   o   
o                   o                   o   
o                                       o   
  o                                     o   
  o                                     o   
    o                                 o     
    o                               o       
      o                           o         
        o                         o         
          o                     o           
            o                 o             
              o             o               
              o           o                 
                o       o                   
                  o     o                   
                    o o                     
```
{% endcapture %}
{%- include collapsed_block.html summary="Полный вывод" content=collapsed_md -%}

Рисуем строку текста в виде чёрно-белого изображения, затем обходим пиксели этого изображения и
выводим их текстом в консоль построчно. Рисуем только центральную часть изображения с текстом,
пустые строки пикселей не выводим.

```java
// рисуем текст в форме картинки и картинку в форме текста
public static void printTextImage(String str, Font font) {
  FontRenderContext ctx = // контекст отображения шрифта
      new FontRenderContext(font.getTransform(), false, false);
  // получаем размеры картинки с текстом при отрисовке
  Rectangle bnd = font.getStringBounds(str, ctx).getBounds();
  // создаём новое бинарное чёрно-белое изображение
  BufferedImage image = new BufferedImage(
      bnd.width, bnd.height, BufferedImage.TYPE_BYTE_BINARY);
  // включаем режим редактирования нового изображения
  Graphics2D graphics = image.createGraphics();
  // шрифт для отрисовки, сглаживание не используем
  graphics.setFont(font);
  // рисуем картинку с текстом
  graphics.drawString(str, bnd.x, -bnd.y);
  // отключаем режим редактирования
  graphics.dispose();
  // выводим заголовок
  System.out.println(
      font.getFontName()+", "+font.getSize()+", символы: "+str);
  // обходим пиксели построчно и выводим непустые строки
  for (int y = 0; y < bnd.height; y++) {
    StringBuilder line = new StringBuilder();
    for (int x = 0; x < bnd.width; x++)
      line.append(image.getRGB(x, y) == -1 ? "o " : "  ");
    // рисуем только непустые строки
    if (line.indexOf("o") != -1) System.out.println(line);
  }
}
```
```java
// запускаем программу и выводим результат
public static void main(String[] args) {
  printTextImage("♡", new Font(Font.MONOSPACED, Font.PLAIN, 22));
  printTextImage("♡", new Font(Font.MONOSPACED, Font.PLAIN, 28));
  printTextImage("♡", new Font(Font.MONOSPACED, Font.PLAIN, 36));
}
```

В последнем примере используется библиотека Java AWT.

{% capture collapsed_md %}
```java
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.image.BufferedImage;
```
{% endcapture %}
{%- include collapsed_block.html summary="Необходимые импорты" content=collapsed_md -%}
